# Paparazzi center utilities
#
# Copyright (C) 2016 ENAC, Florian BITARD (intern student)
#
# This file is part of paparazzi.
#
# paparazzi is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# paparazzi is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with paparazzi; see the file COPYING.  If not, write to
# the Free Software Foundation, 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.

###############################################################################
# [Imports]

import ui.main_window as ui
import dialogs as dial
import lib.console as cs
import lib.database as db
import lib.environment as env
import lib.gui as gui
import parser
import processes as proc

import PyQt5.QtCore as Core
import PyQt5.QtWidgets as Widgets
import functools
import logging
import os
import sys
import re
import shutil

###############################################################################
# [Constants]

DEFAULT_TOOL_ICON = "default_tool_icon.svg"

LOGGER = logging.getLogger("[HMI]")

CONF_PATH = env.PAPARAZZI_CONF
SET_SYMBOLIC_LINK = CONF_PATH + "/conf.xml"
CONF_XML_COPY = CONF_PATH + "/conf_copy.xml"

OK_QPIXMAP = "icons/dialog-apply.svg"
ERROR_QPIXMAP = "icons/dialog-error.svg"
START_ICON = "icons/media-playback-start.svg"
STOP_ICON = "icons/process-stop.svg"
CHANGED_ICON = "icons/dialog-warning-symbolic.svg"
ICONS_TOOLS_PATH = "data/pictures/tools_icons/"

UNKNOWN_VALUE = "---"

PROGRESS_BAR_LINE = " "
PROGRESS_BAR_CURSOR = "***"
PROGRESS_BAR_SPEED = 10


###############################################################################
# [Functions] System functions

def control_cwd():
    """
    -> Get the current directory path and log it.
    """
    cwd = os.getcwd()
    LOGGER.info("Current directory : '%s'.", cwd)
    return cwd


def point_symbol_link_to(set_file):
    """
    :param set_file:
    -> Make the 'conf.xml' symbolic file point to the given 'set_file'.
    """
    if os.path.exists(SET_SYMBOLIC_LINK):
        os.remove(SET_SYMBOLIC_LINK)
    os.symlink(set_file, SET_SYMBOLIC_LINK)
    LOGGER.debug("'%s' -> '%s'.", SET_SYMBOLIC_LINK, set_file)


def init_conf_xml_path():
    """
    -> Create a copy of the conf.xml file if it's an existing and real file.
    -> Make the conf.xml file point to the copy created.
    """
    if os.path.exists(SET_SYMBOLIC_LINK) \
            and not os.path.islink(SET_SYMBOLIC_LINK):
        shutil.copy(SET_SYMBOLIC_LINK, CONF_XML_COPY)
        point_symbol_link_to(CONF_XML_COPY)


###############################################################################
# [Functions] Widgets management functions

def set_widgets_visibility(widgets, set_visible_bool):
    """
    :param widgets:
    :param set_visible_bool:
    -> Set a list of widgets visible or not together.
    """
    for widget in widgets:
        widget.setVisible(set_visible_bool)


def set_widgets_availability(widgets, set_available_bool):
    """
    :param widgets:
    :param set_available_bool:
    -> Set a list of widgets available or not together.
    """
    for widget in widgets:
        widget.setEnabled(set_available_bool)


def set_items_icons(list_widget_items, qicon):
    """
    :param list_widget_items:
    :param qicon:
    -> Put the same QIcon to items of a QListWidget.
    """
    for item in list_widget_items:
        item.setIcon(qicon)


def clear_widgets(widgets):
    """
    :param widgets:
    -> Clear each widget in a list of widgets.
    """
    for widget in widgets:
        widget.clear()


def clear_list_widgets_selection(widgets):
    """
    :param widgets:
    -> Clear the selection of each widget in a list of QListWidget widgets.
    """
    for widget in widgets:
        widget.clearSelection()
        widget.clearFocus()


###############################################################################
# [Hmi class]

class Hmi(Widgets.QMainWindow):
    """Class to manage the main HMI behavior."""
    def __init__(self):
        """
        -> Creation of a MainWindow object generated by the command :
        'pyuic5 *.ui -o *.py' from the PyQt5 HMI designed under QtDesigner.
        -> Creation of Dialog objects used from the main HMI.
        -> Declaration of widgets groups for code factorization.
        -> Declaration of HMI private attributes for its own methods.
        """
        super(Hmi, self).__init__()

        self.ui = ui.Ui_MainWindow()
        self.ui.setupUi(self)

        # Existing popups :
        self.settings = None
        self.data_changed_popup = None
        self.credits_popup = None
        self.tutorial_popup = None

        # Shortcuts for lists of widgets :
        self.change_config_widgets = [self.ui.current_airframes,
                                      self.ui.current_settings,
                                      self.ui.current_flight_plan,
                                      self.ui.current_radio,
                                      self.ui.current_telemetry]
        self.display_config_widgets = [self.ui.airframes_overview,
                                       self.ui.settings_overview,
                                       self.ui.flight_plan_overview,
                                       self.ui.radio_overview,
                                       self.ui.telemetry_overview,
                                       self.ui.airframes_overview_2,
                                       self.ui.settings_overview_2,
                                       self.ui.flight_plan_overview_2,
                                       self.ui.radio_overview_2,
                                       self.ui.telemetry_overview_2]
        self.flight_plan_buttons = [self.ui.open_gui,
                                    self.ui.select_kml]
        self.session_widgets = [self.ui.session,
                                self.ui.session_overview_1,
                                self.ui.session_overview_2]
        self.display_programs_widgets = [self.ui.programs_overview_1,
                                         self.ui.programs_overview_2]
        self.target_widgets = [self.ui.quick_target, self.ui.target]
        self.messages_level_widgets = [self.ui.important,
                                       self.ui.custom, self.ui.all]
        self.log_filters_widgets = [self.ui.display_default,
                                    self.ui.display_info,
                                    self.ui.display_warnings,
                                    self.ui.display_errors]
        self.messages_nb_widgets = [self.ui.info_nb, self.ui.warnings_nb,
                                    self.ui.errors_nb]
        self.start_all_buttons = [self.ui.start_all_button,
                                  self.ui.quick_restart,
                                  self.ui.quick_restart_3]
        self.kill_all_buttons = [self.ui.kill_all_button, self.ui.quick_kill,
                                 self.ui.quick_kill_2]

        # Icons used in widgets (can't be constants because qpixmap objects
        # must be declared into a QApplication) :
        self.ok_qpixmap = gui.generate_qpixmap(OK_QPIXMAP)
        self.error_qpixmap = gui.generate_qpixmap(ERROR_QPIXMAP)
        self.start_qicon = gui.generate_qicon(START_ICON)
        self.stop_qicon = gui.generate_qicon(STOP_ICON)
        self.changed_qicon = gui.generate_qicon(CHANGED_ICON)

        # Progress bar initialization :
        self.max_result_field_size = self.ui.build_result.width() // 10
        self.progress_bar_to_left = False

        # HMI parameters initialization (could be set in settings menu) :
        self.dev_mode = False

        self.current_log_filter = None

        self.data = None
        self.current_set = None
        self.current_config = None
        self.current_item = None

        self.current_target = None
        self.simulation_targets_names = ['nps', 'sim']
        self.change_target_bool = True

        self.current_device = None
        self.current_session = None
        self.current_program = None
        self.current_option = None

        self.run_version = None
        self.build_version = None

        self.configurations_changed = {}
        self.sessions_changed = {}

        self.build = None
        self.build_running = False
        self.clean = None
        self.clean_running = False
        self.upload = None
        self.upload_running = False

        self.running_programs = {}

        self.console = cs.Console(self.ui.console)

        # Logger output redirected to a special stream to allow an integrated
        # console display (and logger level could be set in settings menu) :
        self.logger_stream = proc.LoggerStream()
        self.logger_stream.logger_log_sent.connect(self.write_log_in_console)
        logging.basicConfig(level=logging.INFO, stream=self.logger_stream)

###############################################################################
# [Hmi methods] Init HMI methods

    def init_hmi_data(self):
        try:
            self.init_hmi_data_core()
        except:
            " if something goes wrong, delete cache and load again (to be improved)"
            LOGGER.error("ERROR while load HMI cache"
                         "Original message : '%s'.", sys.exc_info()[0])
            print("HMI error in cache, load default instead")
            parser.delete_cache()
            self.init_hmi_data_core()
            
        # Run and build Paparazzi versions found by existing program
        # './paparazzi_version' and file './var/build_version.txt' :
        run_version_cmd = env.RUN_VERSION_EXE
        self.run_version = os.popen(run_version_cmd).readline().strip()
        build_version_cmd = " ".join(["cat", env.BUILD_VERSION_FILE])
        self.build_version = os.popen(build_version_cmd).readline().strip()

    def init_hmi_data_core(self):
        """
        -> Initialization of a 'Data' object with all necessary data from XML
        files found in the 'CONF_PATH' Paparazzi UAV directory.
        -> Loading of cache data (last settings used) to restore them.
        -> Initialization of main HMI widgets with necessary data found.
        """
        init_conf_xml_path()
        self.data = parser.Data(CONF_PATH)

        # Cache parameters extracted from the cache dictionary :
        last_geometry = self.data.cache[parser.LAST_GEOMETRY].split(" ")
        last_x, last_y, last_width, last_height = map(int, last_geometry)
        self.setGeometry(last_x, last_y, last_width, last_height)

        last_set_name = self.data.cache[parser.LAST_SET]
        self.current_set = self.data.sets[last_set_name]

        point_symbol_link_to(self.current_set.name)

        last_config_name = self.data.cache[parser.LAST_CONFIG]
        self.current_config = self.data.configurations[last_config_name]

        last_target_name = self.data.cache[parser.LAST_TARGET]
        self.current_target = self.current_config.targets[last_target_name]

        self.ui.upload.setEnabled(self.current_target.name
                                  not in self.simulation_targets_names)

        last_device_name = self.data.cache[parser.LAST_DEVICE]
        self.current_device = self.data.devices[last_device_name]

        last_session_name = self.data.cache[parser.LAST_SESSION]
        self.current_session = self.data.sessions[last_session_name]

        last_log_filters = self.data.cache[parser.LAST_LOG_FILTERS].split(" ")
        last_level = last_log_filters[0]
        last_default, last_info, last_warning, last_error =\
            map(int, last_log_filters[1:])
        self.current_log_filter = cs.LogFilter(last_level,
                                               last_default, last_info,
                                               last_warning, last_error)

        
    def init_hmi_widgets(self):
        """
        -> Initialization of all the HMI widgets content.
        -> Initialization of existing popups.
        """
        # Update functions used for initialization :
        self.update_home_button()
        self.update_mode_button()
        self.update_versions()
        self.update_set_combo()
        self.update_config_combo()
        self.update_config_items_lists()
        self.update_target_combos(self.target_widgets)
        self.update_log_filters()
        self.change_messages_level()
        self.update_device_combo()
        self.update_session_combos()
        self.update_programs_list()
        self.update_program_options_list()
        self.init_tools_menu()

        # Popup initialization :
        self.settings = dial.SettingsManager()
        self.data_changed_popup = dial.Popup(dial.DATA_CHANGED_POPUP_TYPE)
        self.credits_popup = dial.Popup(dial.CREDITS_POPUP_TYPE)
        self.tutorial_popup = dial.Popup(dial.TUTORIAL_POPUP_TYPE)

    def init_all_hmi(self):
        """
        -> Centralize all initializations before the mainwindow is shown.
        """
        control_cwd()
        LOGGER.info("HMI init in progress...\n")
        print("\nHMI init in progress...")
        self.init_hmi_data()
        self.init_hmi_widgets()
        self.connect_signals()
        LOGGER.info("HMI init finished.\n")
        print("HMI init finished.\n")

###############################################################################
# [Hmi methods] Connect signals methods

    def connect_signals(self):
        """
        -> Connection of 'triggered' signals from 'QMenu' or 'QAction'.
        -> Connection of 'currentIndexChanged' signals from 'QComboBox'.
        -> Connection of 'clicked' signals from 'QPushButton'.
        """
        #######################################################################
        # Actions in menus

        self.ui.actionQuit_Paparazzi_UAV.triggered.connect(self.close)

        self.ui.actionSetManager.triggered.connect(self.settings.show)
        self.ui.actionSave_2.triggered.connect(self.save_current_config)

        self.ui.actionSave_3.triggered.connect(self.save_current_session)

        self.ui.actionFull_screen.triggered.connect(self.fullscreen_view)
        self.ui.actionHide_overviews.triggered.connect(self.toggle_overview)

        self.ui.actionAbout_Paparazzi_UAV.triggered.connect(
            self.credits_popup.show)
        self.ui.actionTutorial.triggered.connect(self.tutorial_popup.show)

        #######################################################################
        # Equipment tab

        for list_widget in self.change_config_widgets:
            list_widget.itemSelectionChanged.connect(functools.partial(
                self.change_current_item, list_widget))
        self.ui.current_settings.itemClicked.connect(
            self.change_setting_check_state)

        self.ui.current_set.currentIndexChanged.connect(self.change_current_set)
        self.ui.current_configuration.currentIndexChanged.connect(
            self.change_current_config)

        self.ui.remove_item.clicked.connect(self.remove_item_from_config)
        self.ui.add_item.clicked.connect(self.open_item_file_chooser)
        self.ui.edit.clicked.connect(self.open_gedit)

        self.ui.quick_build.clicked.connect(self.quick_build)

        #######################################################################
        # Build / flash tab

        for combo_widget in self.target_widgets:
            combo_widget.currentIndexChanged.connect(functools.partial(
                self.change_current_target, combo_widget))

        for widget in self.log_filters_widgets:
            widget.clicked.connect(self.change_sensitivity_filters)

        for widget in self.messages_level_widgets:
            widget.clicked.connect(self.change_messages_level)

        self.ui.clean.clicked.connect(self.clean_config)
        self.ui.build.clicked.connect(self.build_config)
        self.ui.upload.clicked.connect(self.flash_device)
        self.ui.show_console.clicked.connect(functools.partial(
            self.switch_to_tab, 3))

        self.ui.device.currentIndexChanged.connect(self.change_current_device)

        #######################################################################
        # Session tab

        for combo_widget in self.session_widgets:
            combo_widget.currentIndexChanged.connect(functools.partial(
                self.change_current_session, combo_widget))

        self.ui.programs.itemSelectionChanged.connect(
            self.change_current_program)
        self.ui.options.itemSelectionChanged.connect(
            self.change_current_option)

        self.ui.play_stop_program.clicked.connect(functools.partial(
            self.run_program, None))
        for button in self.start_all_buttons:
            button.clicked.connect(self.start_all_programs)
        for button in self.kill_all_buttons:
            button.clicked.connect(self.kill_all_programs)

        self.ui.remove_program.clicked.connect(
            self.remove_program_from_session)
        self.ui.remove_option.clicked.connect(self.remove_option_from_program)
        self.ui.add_option.clicked.connect(self.add_option_to_program)
        self.ui.options.itemChanged.connect(self.edit_current_option)

        #######################################################################
        # Console tab

        self.ui.clean_console.clicked.connect(self.clean_console)

        #######################################################################
        # Other widgets connected

        self.ui.open_home_terminal.clicked.connect(self.open_terminal)
        self.ui.switch_mode.clicked.connect(self.switch_current_mode)

###############################################################################
# [Hmi methods] Reimplemented methods

    def closeEvent(self, event):
        """
        :param event: QEvent object automatically filled when 'closed' signal
        caught.
        -> Called automatically when when 'closed' signal caught.
        -> Kill all processes still running.
        -> Update the cache file.
        -> Call the real mainWindow closeEvent to finish the 'app.exec_'
        events loop.
        """
        for process in [self.clean, self.build, self.upload]:
            #              + list(self.running_programs.values()):
            # /!\ REQUIREMENT : DON'T KILL SESSION PROCESSES WHEN THE
            # APPLICATION EXITS...
            if process is not None:
                try:
                    process.subprocess.kill()
                except ProcessLookupError:
                    print("Process : '%s' already stopped. "
                          "Can't be killed again !\n" % process.name)
                else:
                    print("Process : '%s' killed !\n" % process.name)

        # Ask for a confirmation if some data are unsaved and quit saving
        # these data :
        if self.configurations_changed != {} or self.sessions_changed != {}:
            if self.sessions_changed != {parser.SIMULATION_NAME:
                                         parser.SIMULATION_SESSION} \
                and self.sessions_changed != {parser.REPLAY_NAME:
                                              parser.REPLAY_SESSION}:
                self.data_changed_popup.show()
                self.data_changed_popup.accepted.connect(
                    self.save_all_changed_data)
                self.data_changed_popup.rejected.connect(
                    self.save_cache_data)
            else:
                print("A special in-code defined session (Session or Replay) "
                      "has been changed and could not have been saved...\n")
                self.save_cache_data()
        else:
            self.save_cache_data()

###############################################################################
# [Hmi methods] Update widgets methods

    def update_home_button(self):
        self.ui.open_home_terminal.setText(env.PAPARAZZI_HOME)

    def update_mode_button(self):
        if self.dev_mode:
            self.ui.switch_mode.setText("Developer")
        else:
            self.ui.switch_mode.setText("Standard")

    def update_versions(self):
        for field, value in zip([self.ui.run_version, self.ui.build_version],
                                [self.run_version, self.build_version]):
            if value is not None:
                field.setText(value)
            else:
                field.setText(UNKNOWN_VALUE)

    def switch_to_tab(self, tab_index):
        self.ui.main_tab.setCurrentIndex(tab_index)

    def init_tools_menu(self):
        """
        -> Clear the 'Tools' menu actions (menubar).
        -> Fill the 'Tools' menu with the names found in 'control_panel.xml'
        -> Connect each action to the corresponding tool process.
        """
        # The tools are sorted first by whether they are favorite or not, then by their names
        for tool in sorted(self.data.tools.values(), key=lambda tool: (not tool.favorite, tool.name)):
            if not tool.blacklisted:
                command = functools.partial(self.add_program_to_session, tool)
                icon_name = tool.icon if tool.icon is not None else DEFAULT_TOOL_ICON
                icon_path = "/".join([env.PAPARAZZI_HOME, ICONS_TOOLS_PATH, icon_name])
                self.ui.tools_menu.add_item(tool.name, icon_path, command)

    def fullscreen_view(self):
        if self.isMaximized():
            self.resize(800, 600)
        else:
            self.showMaximized()

    def toggle_overview(self):
        if self.ui.dockWidget.isVisible():
            self.ui.dockWidget.hide()
            self.ui.actionHide_overviews.setText("Show overviews")
        else:
            self.ui.dockWidget.show()
            self.ui.actionHide_overviews.setText("Hide overviews")

    def update_set_combo(self):
        """
        -> Clear the 'Set' combobox.
        -> Fill the combobox with the names found in 'conf.xml'.
        """
        self.ui.current_set.clear()
        sorted_names = parser.sorted_sets_names(self.data.sets)
        self.ui.current_set.addItems(sorted_names)
        self.ui.current_set.setCurrentText(self.current_set.name)

    def update_config_combo(self):
        """
        -> Clear the 'Configuration' combobox.
        -> Fill the combobox with the configurations names corresponding to the
        current selected set.
        -> Save the current settings check state when configuration changed.
        (allows to the configuration to be recovered when selected again)
        """
        self.ui.current_configuration.clear()
        sorted_names = parser.sorted_current_configs_names(
            self.data.configurations,
            self.current_set)
        self.ui.current_configuration.addItems(sorted_names)
        self.ui.current_configuration.setCurrentText(self.current_config.name)

    def update_config_items_lists(self):
        """
        -> Disable or hide widgets and buttons to init config.
        -> Set config ID and color.
        -> Set config items by category.
        -> Make 'settings' items checkable and set check-boxes.
        """
        set_widgets_visibility(self.display_config_widgets +
                               self.change_config_widgets, False)
        set_widgets_availability([self.ui.remove_item,
                                  self.ui.edit], False)
        set_widgets_visibility(self.flight_plan_buttons, False)

        self.ui.current_id.setText(self.current_config.id)
        color = gui.generate_qcolor(self.current_config.color[0])
        palette = gui.generate_widget_palette(self.ui.current_color, color)
        self.ui.current_color.setPalette(palette)

        clear_widgets(self.change_config_widgets)
        for widget, values in zip(self.change_config_widgets,
                                  [self.current_config.airframes,
                                   self.current_config.settings +
                                      self.current_config.modules,
                                   self.current_config.flight_plan,
                                   self.current_config.radio,
                                   self.current_config.telemetry]):
            for value in values:
                value = value.strip()
                if value:
                    widget.addItem(value)
            if values:
                set_widgets_visibility([widget], True)

        for i in range(self.ui.current_settings.count()):
            item = self.ui.current_settings.item(i)
            item.setFlags(Core.Qt.ItemIsUserCheckable | Core.Qt.ItemIsEnabled |
                          Core.Qt.ItemIsSelectable)
            if item.text().startswith("["):
                item.setText(item.text().strip("[]"))
                item.setCheckState(Core.Qt.Unchecked)
            else:
                item.setCheckState(Core.Qt.Checked)

        self.update_config_overview_lists()

    def update_config_overview_lists(self):
        """
        -> Hide all the configuration overview widgets.
        -> Clear these widgets.
        -> Copy the content of the real configuration QListWidget widgets.
        -> Keep only the basename of long items (put full name in tooltip).
        -> Display the widgets only if not empty (save some vertical space).
        """
        set_widgets_visibility(self.display_config_widgets, False)
        clear_widgets(self.display_config_widgets)
        for input_widget, display_widget in zip(self.change_config_widgets*2,
                                                self.display_config_widgets):
            items_full_names = []
            for i in range(input_widget.count()):
                item_full_name = input_widget.item(i).text()
                item_name = os.path.splitext(
                    os.path.basename(item_full_name))[0]
                display_widget.addItem(item_name)
                items_full_names.append(item_full_name)
            display_widget.setToolTip("\n".join(items_full_names))
            if display_widget.count() != 0:
                display_widget.setVisible(True)

    def update_target_combos(self, combo_widgets):
        """
        :param combo_widgets:
        -> Clear the 2 'Target' combo widgets.
        -> Fill them with the targets available for the current configuration.
        """
        clear_widgets(combo_widgets)
        sorted_names = parser.sorted_current_targets_names(
            self.current_config.targets)
        for widget in combo_widgets:
            widget.addItems(sorted_names)
            widget.setCurrentText(self.current_target.name)

    def update_log_filters(self):
        """
        -> Load the last filters from the cache.
        -> Disable the filters if the level is minimum or maximum.
        -> Set the filters check state by the current LogFilter object
        properties.
        """
        if self.current_log_filter.level == cs.MINIMAL_MESSAGES_LEVEL:
            self.ui.important.setChecked(True)
            set_widgets_availability(self.log_filters_widgets, False)
        elif self.current_log_filter.level == cs.ALL_MESSAGES_LEVEL:
            self.ui.all.setChecked(True)
            set_widgets_availability(self.log_filters_widgets, False)
        else:
            self.ui.custom.setChecked(True)
            set_widgets_availability(self.log_filters_widgets, True)
        self.ui.display_default.setChecked(self.current_log_filter.default)
        self.ui.display_info.setChecked(self.current_log_filter.info)
        self.ui.display_warnings.setChecked(self.current_log_filter.warning)
        self.ui.display_errors.setChecked(self.current_log_filter.error)

    def update_device_combo(self):
        """
        -> Clear the 'Device' combobox.
        -> Search compatible boards for current target.
        -> Update the combo-box with devices corresponding to each board.
        -> Deal with empty devices list because of default Wi-Fi flash.
        """
        self.ui.device.clear()
        current_devices = []
        for device in self.data.devices.values():
            if device.name != parser.DEFAULT_DEVICE_NAME:
                for board_regex in device.boards_regex:
                    found_match = re.search(board_regex, self.current_target.board)
                    if found_match:
                        current_devices.append(device)
                        break
            else:
                current_devices.append(device)
        sorted_names = parser.sorted_current_devices_names(current_devices)
        self.ui.device.addItems(sorted_names)
        self.ui.device.setCurrentText(self.current_device.name)
        self.ui.device.setEnabled(True)

    def update_session_combos(self):
        """
        -> Clear the 'Session' combobox.
        -> Fill it with the sessions found in 'control_panel.xml'
        """
        clear_widgets(self.session_widgets)
        sorted_names = parser.sorted_sessions_names(self.data.sessions)
        for widget in self.session_widgets:
            widget.addItems(sorted_names)
            sep_index = sorted_names.index(parser.SESSIONS_COMBO_SEP)
            variant = Core.QVariant(not Core.Qt.ItemIsEnabled)
            widget.setItemData(sep_index, variant, Core.Qt.UserRole - 1)
            widget.setCurrentText(self.current_session.name)

    def update_programs_list(self):
        """
        -> Clear the programs QListWidget.
        -> Fill it with the programs available for the current session.
        -> Update the programs overview widgets with the same programs.
        -> Init the play/stop program button HMI.
        """
        self.ui.programs.clear()
        self.current_program = None
        sorted_names = parser.sorted_current_programs_names(
            self.current_session)
        self.ui.programs.addItems(sorted_names)
        for i in range(self.ui.programs.count()):
            self.ui.programs.item(i).setIcon(self.stop_qicon)
        self.update_programs_overviews()
        self.update_program_button()
        self.update_program_options_list()

        set_widgets_availability([self.ui.play_stop_program,
                                  self.ui.remove_program,
                                  self.ui.add_option] +
                                 self.kill_all_buttons, False)

    def update_programs_overviews(self):
        """
        -> Clear the programs overview QListWidget widgets.
        -> Fill them with the current programs widget items.
        -> Set their icon to stopped state.
        """
        clear_widgets(self.display_programs_widgets)
        for input_widget, display_widget in zip([self.ui.programs]*2,
                                                self.display_programs_widgets):
            for i in range(input_widget.count()):
                display_widget.addItem(input_widget.item(i).text())
                item = display_widget.item(i)
                item.setIcon(self.stop_qicon)
                item.setFlags(Core.Qt.ItemIsEnabled)

    def update_program_options_list(self):
        """
        -> Clear the options QListWidget.
        -> Fill them with the current options of the selected program if
        possible (and detect the option shape : tuple or str).
        """
        self.ui.options.clear()
        self.current_option = None
        if self.current_program is not None:
            options = self.current_program[1].options
            for option in options:
                option_str = option
                if type(option) is tuple:
                    option_str = " ".join(option)
                self.ui.options.addItem(option_str)
            for i in range(self.ui.options.count()):
                item = self.ui.options.item(i)
                item.setFlags(Core.Qt.ItemIsEditable | Core.Qt.ItemIsEnabled |
                              Core.Qt.ItemIsSelectable)
        self.ui.remove_option.setEnabled(False)

    def init_build_flash_hmi(self):
        """
        -> Kill the build processes if already running.
        -> Clear the build widgets.
        """
        clear_widgets([self.ui.build_result_icon,
                       self.ui.build_result,
                       self.ui.flash_resut_icon,
                       self.ui.flash_result] + self.messages_nb_widgets)

    def update_flags_nb_hmi(self, compilation_object):
        """
        :param compilation_object:
        -> Update the flags number QLabel widgets at each log sent to the
        console.
        """
        self.ui.errors_nb.setText(str(
            compilation_object.flags[cs.ERROR_FLAG]))
        self.ui.warnings_nb.setText(str(
            compilation_object.flags[cs.WARNING_FLAG]))
        self.ui.info_nb.setText(str(
            compilation_object.flags[cs.INFO_FLAG]))

###############################################################################
# [Hmi methods] Clean methods

    def set_clean_started_hmi(self):
        """
        -> Turn HMI to clean running state.
        """
        self.init_build_flash_hmi()
        self.clean_running = True
        set_widgets_availability([self.ui.build, self.ui.upload,
                                  self.ui.quick_build] +
                                 self.target_widgets, False)
        self.ui.clean.setText("Stop !")
        self.setCursor(gui.generate_qcursor(gui.WAIT_CURSOR_SHAPE))

    def set_clean_stopped_hmi(self):
        """
        -> Turn HMI to clean stopped state.
        -> Display the result of the clean process.
        -> Switch to the build / flash tab.
        """
        self.clean_running = False
        set_widgets_availability([self.ui.build, self.ui.quick_build,
                                  self.ui.clean] +
                                 self.target_widgets, True)
        if self.current_target.name not in self.simulation_targets_names:
            self.ui.upload.setEnabled(True)
        self.ui.clean.setText("Clean")
        self.setCursor(gui.generate_qcursor(gui.NORMAL_CURSOR_SHAPE))
        self.update_flags_nb_hmi(self.clean)
        errors = self.clean.flags[cs.ERROR_FLAG]
        if self.clean.exit_code == proc.INTERRUPTED_EXIT_CODE:
            self.ui.build_result.setText("Clean aborted by user !")
            self.ui.build_result_icon.setPixmap(self.error_qpixmap)
        elif errors != 0 or self.clean.exit_code == proc.DEFAULT_EXIT_CODE:
            self.ui.build_result.setText("Error(s) found " +
                                         "during clean. " +
                                         "Please try again !")
            self.ui.build_result_icon.setPixmap(self.error_qpixmap)
        elif self.clean.exit_code == proc.SUCCESS_EXIT_CODE:
            self.ui.build_result.setText("Clean done with success !")
            self.ui.build_result_icon.setPixmap(self.ok_qpixmap)
        else:
            self.ui.build_result.setText("Unknown error !")
            self.ui.build_result_icon.setPixmap(self.error_qpixmap)
        self.clean = None
        self.switch_to_tab(1)

###############################################################################
# [Hmi methods] Build methods

    def set_build_started_hmi(self):
        """
        -> Turn HMI to build running state.
        """
        self.init_build_flash_hmi()
        self.build_running = True
        set_widgets_availability([self.ui.clean, self.ui.upload,
                                  self.ui.quick_build] +
                                 self.target_widgets, False)
        self.ui.build.setText("Stop !")
        self.setCursor(gui.generate_qcursor(gui.WAIT_CURSOR_SHAPE))

    def set_build_stopped_hmi(self):
        """
        -> Turn HMI to build stopped state.
        -> Display the result of the build process.
        -> Switch to the build / flash tab.
        """
        self.build_running = False
        set_widgets_availability([self.ui.clean, self.ui.quick_build,
                                  self.ui.build] +
                                 self.target_widgets, True)
        if self.current_target.name not in self.simulation_targets_names:
            self.ui.upload.setEnabled(True)
        self.ui.build.setText("Build")
        self.setCursor(gui.generate_qcursor(gui.NORMAL_CURSOR_SHAPE))
        self.update_flags_nb_hmi(self.build)
        errors = self.build.flags[cs.ERROR_FLAG]
        if self.build.exit_code == proc.INTERRUPTED_EXIT_CODE:
            self.ui.build_result.setText("Build aborted by user !")
            self.ui.build_result_icon.setPixmap(self.error_qpixmap)
        elif errors != 0 or self.build.exit_code == proc.DEFAULT_EXIT_CODE:
            self.ui.build_result.setText("Error(s) found " +
                                         "during build. " +
                                         "Please try again !")
            self.ui.build_result_icon.setPixmap(self.error_qpixmap)
        elif self.build.exit_code == proc.SUCCESS_EXIT_CODE:
            self.ui.build_result.setText("Build done with success !")
            self.ui.build_result_icon.setPixmap(self.ok_qpixmap)
        else:
            self.ui.build_result.setText("Unknown error !")
            self.ui.build_result_icon.setPixmap(self.error_qpixmap)
        self.build = None
        self.switch_to_tab(1)

###############################################################################
# [Hmi methods] Flash methods

    def set_flash_started_hmi(self):
        """
        -> Turn HMI to flash running state.
        """
        self.init_build_flash_hmi()
        self.upload_running = True
        set_widgets_availability([self.ui.clean, self.ui.build,
                                  self.ui.quick_build, self.ui.device] +
                                 self.target_widgets, False)
        self.ui.upload.setText("Stop !")
        self.setCursor(gui.generate_qcursor(gui.WAIT_CURSOR_SHAPE))
        clear_widgets([self.ui.flash_resut_icon,
                       self.ui.flash_result])

    def set_flash_stopped_hmi(self):
        """
        -> Turn HMI to flash stopped state.
        -> Display the result of the upload process.
        -> Switch to the build / flash tab.
        """
        self.upload_running = False
        set_widgets_availability([self.ui.clean, self.ui.build,
                                  self.ui.upload,
                                  self.ui.quick_build, self.ui.device] +
                                 self.target_widgets, True)
        self.ui.upload.setText("Upload")
        self.setCursor(gui.generate_qcursor(gui.NORMAL_CURSOR_SHAPE))
        self.update_flags_nb_hmi(self.upload)
        errors = self.upload.flags[cs.ERROR_FLAG]
        if self.upload.exit_code == proc.INTERRUPTED_EXIT_CODE:
            self.ui.flash_result.setText("Upload aborted by user !")
            self.ui.flash_resut_icon.setPixmap(self.error_qpixmap)
        elif errors != 0 or self.upload.exit_code == proc.DEFAULT_EXIT_CODE:
            self.ui.flash_result.setText("Error(s) found " +
                                         "during upload. " +
                                         "Please try again !")
            self.ui.flash_resut_icon.setPixmap(self.error_qpixmap)
        elif self.upload.exit_code == proc.SUCCESS_EXIT_CODE:
            self.ui.flash_result.setText("Upload done with success !")
            self.ui.flash_resut_icon.setPixmap(self.ok_qpixmap)
        else:
            self.ui.flash_result.setText("Unknown error !")
            self.ui.flash_resut_icon.setPixmap(self.error_qpixmap)
        self.upload = None
        self.switch_to_tab(1)

###############################################################################
# [Hmi methods] Program methods

    def update_program_button(self):
        """
        -> Update the play/stop program button HMI to correct state.
        """
        if self.current_program is not None:
            self.ui.play_stop_program.setEnabled(True)
            if self.current_program[1].name in self.running_programs.keys():
                self.ui.play_stop_program.setIcon(self.stop_qicon)
                self.ui.play_stop_program.setText("Stop")
            else:
                self.ui.play_stop_program.setIcon(self.start_qicon)
                self.ui.play_stop_program.setText("Start")
        else:
            self.ui.play_stop_program.setEnabled(False)
            self.ui.play_stop_program.setIcon(self.start_qicon)

    def set_program_started_hmi(self, program_process):
        """
        :param program_process:
        -> Turn HMI to program running state.
        """
        self.running_programs[program_process.program.name] = program_process

        items = [widget.findItems(program_process.program.name,
                                  Core.Qt.MatchExactly)[0]
                 for widget in [self.ui.programs,
                                self.ui.programs_overview_1,
                                self.ui.programs_overview_2]]
        set_items_icons(items, self.start_qicon)
        self.update_program_button()

        if self.running_programs != {}:
            set_widgets_availability([self.ui.session,
                                      self.ui.session_overview_1,
                                      self.ui.session_overview_2,
                                      self.ui.current_set],
                                     False)
            set_widgets_availability(self.kill_all_buttons, True)
        set_widgets_availability(self.start_all_buttons, False)

    def set_program_stopped_hmi(self, program_process):
        """
        :param program_process:
        -> Turn HMI to program stopped state.
        -> Display the result of the program process.
        -> Switch to the sessions management tab.
        """
        self.running_programs.pop(program_process.program.name)

        items = [widget.findItems(program_process.program.name,
                                  Core.Qt.MatchExactly)[0]
                 for widget in [self.ui.programs,
                                self.ui.programs_overview_1,
                                self.ui.programs_overview_2]]
        set_items_icons(items, self.stop_qicon)
        self.update_program_button()

        if self.running_programs == {}:
            set_widgets_availability([self.ui.session,
                                      self.ui.session_overview_1,
                                      self.ui.session_overview_2,
                                      self.ui.current_set],
                                     True)
            set_widgets_availability(self.kill_all_buttons, False)
        set_widgets_availability(self.start_all_buttons, True)
        self.switch_to_tab(2)

###############################################################################
# [Hmi methods] Write log methods

    def write_log_in_console(self, log, flag, log_type):
        """
        :param log:
        :param flag:
        :param log_type:
        -> Get the logs sent by the processes when a 'log_sent' signal is
        caught.
        -> Treat the log depending if it's an application or a process one.
        -> Put color to the log line and write it into the console.
        """
        if log_type == cs.PROCESS_MESSAGE_TYPE:
            if self.current_log_filter.write_line_decision(flag):
                str_color = cs.BACKGROUNDS_COLORS[flag]
                background_qcolor = gui.generate_qcolor(str_color)
                self.console.write(log,
                                   backgroud_color=background_qcolor)
        elif log_type == cs.APPLICATION_MESSAGE_TYPE:
            if log != "\n":
                str_color = cs.FONTS_COLORS[flag]
                font_qcolor = gui.generate_qcolor(str_color)
                self.console.write(log, font_color=font_qcolor)

    def animate_progress_bar(self):
        """
        -> For all running build / flash processes, animate the progress bar.
        -> Change the text displayed in the result field according to its size
        and the current direction.
        """
        for program, label in zip([self.clean, self.build, self.upload],
                                  [self.ui.build_result, self.ui.build_result,
                                   self.ui.flash_result]):
            if program is not None:
                last_result_text = label.text()
                last_result_text_size = label.fontMetrics().\
                    boundingRect(last_result_text).width()
                field_width = label.width()

                if len(last_result_text) < PROGRESS_BAR_SPEED+1:
                    new_result_text = PROGRESS_BAR_LINE * PROGRESS_BAR_SPEED \
                                      + PROGRESS_BAR_CURSOR
                    self.progress_bar_to_left = False
                elif last_result_text_size > field_width - 100\
                        or self.progress_bar_to_left:
                    new_result_text = last_result_text[PROGRESS_BAR_SPEED:]
                    self.progress_bar_to_left = True
                else:
                    new_result_text = PROGRESS_BAR_LINE * PROGRESS_BAR_SPEED \
                                      + last_result_text
                    self.progress_bar_to_left = False
                label.setText(new_result_text)

###############################################################################
# [Hmi methods] Widgets connected with signals methods

    def switch_current_mode(self):
        """
        -> Real switch but no real effect (no dev mode yet...)
        -> RELOAD MAIN WINDOW WITH NEW WIDGETS ?
        """
        self.dev_mode = not self.dev_mode
        self.update_mode_button()

    def change_current_set(self):
        """
        -> Get current set when combobox value changed.
        -> Symbolic link to 'config.xml' changed to the new set.
        -> Update configuration and its items.
        """
        self.save_all_changed_configurations()
        current_set_name = self.ui.current_set.currentText()
        if current_set_name:
            self.current_set = self.data.sets[current_set_name]
            point_symbol_link_to(self.current_set.name)
            self.update_config_combo()
            self.update_config_items_lists()

    def change_current_config(self):
        """
        -> Get current configuration when combobox value changed.
        -> Update target and device values.
        -> Stop the build processes if some running.
        -> Init the build / flash HMI to be ready to a new process.
        """
        current_config_name = self.ui.current_configuration.currentText()
        if current_config_name:
            self.current_config = self.data.configurations[current_config_name]
            self.update_config_items_lists()
            self.update_target_combos([self.ui.target])
            self.update_device_combo()
            self.init_build_flash_hmi()

    def change_current_item(self, list_widget):
        """
        :param list_widget:
        -> Update the current selected item : (widget, item, object).
        -> Clear the others selected items in other categories.
        -> Update the configuration management HMI (buttons).
        -> Make the flight plan buttons appear or disappear.
        -> Update all the check states of the settings list if the current
        selected item is a setting.
        """
        current_selected_item = list_widget.currentItem()
        if current_selected_item is not None:
            current_item_text = current_selected_item.text()
            self.current_item = (list_widget,
                                 current_selected_item,
                                 current_item_text)
            widgets_to_clear = [_ for _ in self.change_config_widgets]
            widgets_to_clear.remove(list_widget)
            clear_list_widgets_selection(widgets_to_clear)
            set_widgets_availability([self.ui.remove_item,
                                      self.ui.edit], True)
            set_widgets_visibility(self.flight_plan_buttons,
                                   list_widget.objectName() ==
                                   "current_flight_plan")

    def change_current_target(self, combo_widget):
        """
        :param combo_widget:
        -> Update the current target and the devices combobox (related).
        -> Possible to change the target from 2 widgets. That's why the
        solution found is strange...
        """
        current_target_name = combo_widget.currentText()
        if current_target_name and self.change_target_bool:
            self.current_target = self.current_config.targets[
                current_target_name]
            self.ui.upload.setEnabled(self.current_target.name
                                      not in self.simulation_targets_names)
            other_widgets = [_ for _ in self.target_widgets]
            other_widgets.remove(combo_widget)
            # TODO BETTER : AVOID THE SIGNALS PING-PONG BETWEEN TARGETS WIDGETS
            self.change_target_bool = False
            self.update_target_combos(other_widgets)
            self.change_target_bool = True
            self.update_device_combo()

    def change_sensitivity_filters(self):
        """
        -> Get checkbox values to set the current filters.
        -> Update the filters values for the console.
        """
        self.current_log_filter.set_sensitivity_filter(
            self.ui.display_default.checkState(),
            self.ui.display_info.checkState(),
            self.ui.display_warnings.checkState(),
            self.ui.display_errors.checkState())
        self.update_log_filters()

    def change_messages_level(self):
        """
        -> Get radiobutton value to set the current message level.
        -> Update the level value for the console.
        """
        if self.ui.all.isChecked():
            self.current_log_filter.set_level(cs.ALL_MESSAGES_LEVEL)
            self.current_log_filter.set_sensitivity_filter(2, 2, 2, 2)
        elif self.ui.custom.isChecked():
            self.current_log_filter.set_level(cs.CUSTOM_MESSAGES_LEVEL)
        elif self.ui.important.isChecked():
            self.current_log_filter.set_level(cs.MINIMAL_MESSAGES_LEVEL)
            self.current_log_filter.set_sensitivity_filter(0, 0, 2, 2)
        self.update_log_filters()

    def change_current_device(self):
        """
        -> Get current device when combobox value changed.
        -> If no device available, the default flash mode is WiFi.
        """
        current_device_name = self.ui.device.currentText()
        if current_device_name:
            self.current_device = self.data.devices[current_device_name]

    def change_current_session(self, combo_widget):
        """
        :param combo_widget:
        -> Get current session when combobox value changed.
        -> Update programs HMI for the new selected session.
        -> Deselect the last selected program and clear the options.
        """
        current_session_name = combo_widget.currentText()
        if current_session_name:
            clear_list_widgets_selection([self.ui.programs] +
                                         self.display_programs_widgets)
            self.current_session = self.data.sessions[current_session_name]
            widgets = [_ for _ in self.session_widgets]
            widgets.remove(combo_widget)
            for widget in widgets:
                widget.setCurrentText(self.current_session.name)
            self.update_programs_list()
            self.update_program_options_list()
            self.ui.options.clear()

    def change_current_program(self):
        """
        -> Get current selected program when QListWidget selection changed.
        -> Update options HMI for the new selected program.
        """
        current_selected_item = self.ui.programs.currentItem()
        if current_selected_item is not None:
            item_name = current_selected_item.text()
            if item_name in self.current_session.programs.keys():
                self.current_program = (current_selected_item,
                                        self.current_session.programs[
                                            item_name])
                self.ui.remove_program.setEnabled(item_name not in
                                                  self.running_programs.keys())
                self.ui.add_option.setEnabled(True)
                self.update_program_options_list()
                self.update_program_button()

    def change_current_option(self):
        """
        -> Get the current selected option when QListWidget selection changed.
        -> Enable options management HMI.
        """
        current_selected_item = self.ui.options.currentItem()
        if current_selected_item is not None:
            item_name = current_selected_item.text()
            self.current_option = (current_selected_item, item_name)
            self.ui.remove_option.setEnabled(True)

    def open_item_file_chooser(self):
        """
        -> While left part of equipment tab is not finished, a simple file
        chooser is used to select new equipment items.
        -> Add the selected new item to the correct category if possible.
        """
        file_chooser = Widgets.QFileDialog()
        fc_title = "Select a file to add to '" +\
                   self.current_config.name + "'..."
        fc_dir = env.PAPARAZZI_CONF
        fc_filter = "XML file(*.xml)"
        path, other = file_chooser.getOpenFileName(file_chooser,
                                                   fc_title,
                                                   fc_dir,
                                                   fc_filter)
        if path:
            filename = parser.full_to_conf_path(path)
            self.add_item_to_config(filename)

    def open_gedit(self):
        """
        -> Open a text editor depending on the OS found.
        """
        if self.current_item is not None:

            if env.OS in ["linux", "linux2"]:
                file_path = os.path.join(CONF_PATH, self.current_item[2])
                command = " ".join(["gedit", file_path])
                try:
                    os.popen(command)
                except ChildProcessError:
                    LOGGER.error("Gedit text editor is not available !\n")
                    self.switch_to_tab(3)
            elif env.OS in []:
                pass
            else:
                LOGGER.error("No text file editor found for your OS !\n")
                self.switch_to_tab(3)

    def clean_console(self):
        """
        -> Just clear the console QTextEdit widget.
        """
        self.ui.console.clear()

    def open_terminal(self):
        """
        -> Open a terminal in the Paparazzi home directory depending on the OS
        found.
        """
        if env.OS in ["linux", "linux2"]:
            command_terms = ["gnome-terminal", "--working-directory ",
                             env.PAPARAZZI_HOME]
            command = " ".join(command_terms)
            os.popen(command)
        else:
            LOGGER.error("No terminal shortcut available for your OS !\n")
            self.switch_to_tab(3)

###############################################################################
# [Hmi methods] Processes management (connected with signals) methods

    def quick_build(self):
        """
        -> Clean the current configuration when the quick build button is
        clicked.
        -> Give the information to the clean function that a build process
        is planned after the clean process.
        """
        self.switch_to_tab(1)
        self.clean_config(is_quick_build=True)

    def clean_config(self, is_quick_build=False):
        """
        :param is_quick_build:
        -> Clean the current configuration if not already started.
        -> Else, abort the current clean process and set the HMI to the
        initial state.
        """
        if not self.clean_running:
            self.clean = proc.Process(proc.CLEAN,
                                      configuration=self.current_config)
            if self.clean.check_before_start():
                self.clean.start()
                self.clean.subprocess.finished.connect(self.set_clean_stopped_hmi)
                self.clean.process_log_sent.connect(functools.partial(
                    self.update_flags_nb_hmi, self.clean))
                self.clean.process_log_sent.connect(self.write_log_in_console)
                self.clean.process_log_sent.connect(self.animate_progress_bar)
                self.set_clean_started_hmi()

                if is_quick_build:
                    self.clean.subprocess.finished.connect(functools.partial(
                        self.build_config))
            else:
                LOGGER.error("Incorrect config, can't clean !\n")
                self.switch_to_tab(3)
        else:
            self.clean.process_killed.emit()
            self.ui.clean.setEnabled(False)

    def build_config(self):
        """
        -> Build the current configuration if not already started.
        -> Else, abort the current build process and set the HMI to the
        initial state.
        """
        if not self.build_running:
            self.build = proc.Process(proc.BUILD,
                                      configuration=self.current_config,
                                      target=self.current_target)
            self.save_current_config()

            if self.build.check_before_start():
                self.build.start()
                self.build.subprocess.finished.connect(self.set_build_stopped_hmi)
                self.build.process_log_sent.connect(functools.partial(
                    self.update_flags_nb_hmi, self.build))
                self.build.process_log_sent.connect(self.write_log_in_console)
                self.build.process_log_sent.connect(self.animate_progress_bar)
                self.set_build_started_hmi()
            else:
                LOGGER.error("Incorrect config, can't build !\n")
                self.switch_to_tab(3)
        else:
            self.build.process_killed.emit()
            self.ui.build.setEnabled(False)

    def flash_device(self):
        """
        -> Flash the current configuration if not already started.
        -> Else, abort the current upload process and set the HMI to the
        initial state.
        """
        if not self.upload_running:
            self.upload = proc.Process(proc.UPLOAD,
                                       configuration=self.current_config,
                                       target=self.current_target,
                                       device=self.current_device)
            if self.upload.check_before_start():
                self.upload.start()
                self.upload.subprocess.finished.connect(self.set_flash_stopped_hmi)
                self.upload.process_log_sent.connect(functools.partial(
                    self.update_flags_nb_hmi, self.upload))
                self.upload.process_log_sent.connect(self.write_log_in_console)
                self.upload.process_log_sent.connect(self.animate_progress_bar)
                self.set_flash_started_hmi()
            else:
                LOGGER.error("Incorrect device, can't upload !\n")
                self.switch_to_tab(3)
        else:
            self.upload.process_killed.emit()
            self.ui.upload.setEnabled(False)

    def run_program(self, program):
        """
        :param program:
        -> Run the current selected program if not already started.
        Else, abort it and set the HMI to the initial state.
        """
        if program is None and self.current_program is not None:
            program_item, program = self.current_program
        if program.name not in self.running_programs.keys():
            new_program_proc = proc.Process(proc.PROGRAM,
                                            program=program,
                                            configuration=self.current_config,
                                            target=self.current_target)
            if new_program_proc.check_before_start():
                new_program_proc.start()
                new_program_proc.subprocess.finished.connect(functools.partial(
                    self.set_program_stopped_hmi, new_program_proc))
                new_program_proc.process_log_sent.connect(
                    self.write_log_in_console)
                self.set_program_started_hmi(new_program_proc)
        else:
            self.running_programs[program.name].process_killed.emit()
            self.ui.play_stop_program.setEnabled(False)

    def start_all_programs(self):
        """
        -> Kill all running programs of the current session.
        -> Start all programs not running (so it's a restart).
        -> Keep the current selected program if there's one.
        """
        for program in self.current_session.programs.values():
            if program.name not in self.running_programs.keys():
                self.run_program(program)

    def kill_all_programs(self):
        """
        -> Kill all running programs of the current session.
        -> Keep the current selected program if there's one.
        """
        for program in self.current_session.programs.values():
            if program.name in self.running_programs.keys():
                self.run_program(program)

###############################################################################
# [Hmi methods] Data management (connected with signals) methods

    def change_setting_check_state(self, clicked_item):
        """
        :param clicked_item:
        -> Save the new check state of settings items.
        -> Add the current config to changed configs => ask to save
        before quitting the application.
        """
        if self.current_config is not None\
                and self.current_item is not None:
            list_widget, list_item, item_name = self.current_item
            setting_name = item_name
            setting_state = list_item.checkState()
            # TODO BETTER : NO SOLUTION (OR SIGNAL) FOUND TO DETECT A CLICK ON
            # :    TODO   : 'QListWidgetItem' SO ONLY CURRENT ITEM CHECKSTATE
            # :    TODO   : SHOULD BE CHANGED... (NOT WORKING WELL)
            if clicked_item == list_item:
                if parser.SETTINGS_REF in setting_name:
                    settings_modules = self.current_config.settings
                else:
                    settings_modules = self.current_config.modules
                settings_modules_names = [item.strip("[]") for item in settings_modules]
                setting_index = settings_modules_names.index(setting_name)
                settings_modules.pop(setting_index)
                if not setting_state:
                    setting_name = "[" + setting_name + "]"
                settings_modules.insert(setting_index, setting_name)

                self.configurations_changed[self.current_config.name] = \
                    self.current_config
                index = self.ui.current_configuration.currentIndex()
                self.ui.current_configuration.setItemIcon(index, self.changed_qicon)
            else:
                clicked_item.setCheckState(setting_state)

    def remove_item_from_config(self):
        """
        -> Remove an item from the current selected configuration.
        -> Impossible to remove all airframes because it doesn't make sense.
        """
        if self.current_config is not None \
                and self.current_item is not None:
            list_widget, list_item, item_name = self.current_item
            if not (list_widget is self.ui.current_airframes and
                    len(list_widget) == 1):
                if list_widget == self.ui.current_settings and \
                        not list_item.checkState():
                    item_name = "[" + item_name + "]"

                config_attributes_lists = [self.current_config.airframes,
                                           self.current_config.settings,
                                           self.current_config.modules,
                                           self.current_config.flight_plan,
                                           self.current_config.radio,
                                           self.current_config.telemetry]
                for attributes_list in config_attributes_lists:
                    if item_name in attributes_list:
                        attributes_list.remove(item_name)
                        break

                list_widget.takeItem(list_widget.row(list_item))
                list_widget.clearSelection()
                self.current_item = None
                self.ui.remove_item.setEnabled(False)
                if not list_widget:
                    list_widget.setVisible(False)
                if list_widget is self.ui.current_flight_plan:
                    set_widgets_visibility(self.flight_plan_buttons, False)
                self.update_config_overview_lists()

                self.configurations_changed[self.current_config.name] = \
                    self.current_config
                index = self.ui.current_configuration.currentIndex()
                self.ui.current_configuration.setItemIcon(index,
                                                          self.changed_qicon)
            else:
                LOGGER.error("You can't remove all airframes from a "
                             "configuration !\n")
                self.switch_to_tab(3)

    def add_item_to_config(self, filename):
        """
        :param filename:
        -> Add the selected filename to the correct category if it's possible.
        -> Else, an error is raised in the console.
        -> If a setting is added, it's checked by default.
        """
        if self.current_config is not None:
            keywords = [parser.AIRFRAME_REF, parser.SETTINGS_REF,
                        parser.MODULES_REF, parser.FP_REF,
                        parser.RADIO_REF, parser.TELEMETRY_REF]
            fields = [self.current_config.airframes,
                      self.current_config.settings,
                      self.current_config.modules,
                      self.current_config.flight_plan,
                      self.current_config.radio,
                      self.current_config.telemetry]
            widgets = self.change_config_widgets.copy()
            widgets.insert(2, self.ui.current_settings)

            match_found = False
            for keyword, field, widget in zip(keywords, fields, widgets):
                if keyword in filename:
                    field.append(filename)
                    widget.addItem(filename)
                    last_item = widget.item(widget.count()-1)
                    if widget == self.ui.current_settings:
                        last_item.setFlags(Core.Qt.ItemIsUserCheckable |
                                           Core.Qt.ItemIsEnabled |
                                           Core.Qt.ItemIsSelectable)
                        last_item.setCheckState(Core.Qt.Checked)
                    widget.setCurrentItem(last_item)
                    match_found = True
                    break

            if match_found:
                self.configurations_changed[self.current_config.name] = \
                    self.current_config
                index = self.ui.current_configuration.currentIndex()
                self.ui.current_configuration.setItemIcon(index,
                                                          self.changed_qicon)
            else:
                LOGGER.error("The selected file '%s' is not an acceptable file in"
                             " a configuration !\n", filename)
                self.switch_to_tab(3)

    def remove_program_from_session(self):
        """
        -> Remove the selected program from the current selected session.
        -> Add the current session to changed sessions => ask to save
        before quitting the application.
        """
        if self.current_session is not None \
                and self.current_program is not None:
            program_item, program = self.current_program

            self.current_session.programs.pop(program.name)
            self.update_programs_list()

            self.sessions_changed[self.current_session.name] = \
                self.current_session
            index = self.ui.session.currentIndex()
            self.ui.session.setItemIcon(index, self.changed_qicon)
            self.switch_to_tab(2)

    def add_program_to_session(self, program):
        """
        :param program:
        -> Add a program object (only a copy of a tool from the Tools menu
        for now) to the current selected session.
        """
        if self.current_session is not None:

            new_program_name = parser.find_unused_item_name(
                self.current_session.programs, program.name)
            new_program = db.Program(new_program_name, program.command,
                                     program.options)
            self.current_session.programs[new_program.name] = new_program
            self.update_programs_list()
            new_item = self.ui.programs.findItems(new_program.name,
                                                  Core.Qt.MatchExactly)[0]
            self.ui.programs.setCurrentItem(new_item)

            self.sessions_changed[self.current_session.name] = \
                self.current_session
            index = self.ui.session.currentIndex()
            self.ui.session.setItemIcon(index, self.changed_qicon)
            self.switch_to_tab(2)

    def remove_option_from_program(self):
        """
        -> Remove the selected option from the current selected program.
        """
        if self.current_program is not None \
                and self.current_option is not None:
            program_item, program = self.current_program
            option_item, option = self.current_option

            if type(option) is str:
                program.options.remove(option)
            else:
                program.options.remove(tuple(option.split()))
            self.update_program_options_list()

            self.sessions_changed[self.current_session.name] = \
                self.current_session
            index = self.ui.session.currentIndex()
            self.ui.session.setItemIcon(index, self.changed_qicon)

    def add_option_to_program(self):
        """
        -> Add an option to the current selected program.
        """
        if self.current_session is not None \
                and self.current_program is not None:
            program_item, program = self.current_program
            new_option = ""
            program.options.append(new_option)
            self.update_program_options_list()
            last_item = self.ui.options.item(self.ui.options.count()-1)
            self.ui.options.setCurrentItem(last_item)

            self.sessions_changed[self.current_session.name] = \
                self.current_session
            index = self.ui.session.currentIndex()
            self.ui.session.setItemIcon(index, self.changed_qicon)

    def edit_current_option(self):
        """
        -> Edit the selected option when enter pressed after changes.
        """
        if self.current_program is not None \
                and self.current_option is not None:
            program_item, program = self.current_program

            old_option_item, old_option = self.current_option
            old_option_index = program.options.index(old_option)
            program.options.remove(old_option)
            self.change_current_option()
            new_option_item, new_option = self.current_option
            program.options.insert(old_option_index, new_option)

            self.sessions_changed[self.current_session.name] = \
                self.current_session
            index = self.ui.session.currentIndex()
            self.ui.session.setItemIcon(index, self.changed_qicon)

###############################################################################
# [Hmi methods] Data management : XML writing methods

    def save_current_config(self):
        """
        -> Update the new values of the current configuration in the
        corresponding XML file.
        """
        if self.current_config.name in self.configurations_changed.keys():
            self.configurations_changed.pop(self.current_config.name)
            index = self.ui.current_configuration.currentIndex()
            self.ui.current_configuration.setItemIcon(index,
                                                      gui.generate_qicon())

            LOGGER.info("Saving '%s' configuration...",
                        self.current_config.name)
            parser.save_configuration(self.current_set.name,
                                      self.current_config)
            LOGGER.info("'%s' configuration saved.\n",
                        self.current_config.name)
        else:
            LOGGER.info("Nothing to save, configuration already up-to-date.\n")

    def save_current_session(self):
        """
        -> Update the new values of the current session in the
        corresponding 'control_panel.xml' file.
        -> Deal with the in-code defined sessions (Simulation, Replay).
        """
        if self.current_session.name in self.sessions_changed.keys():
            self.sessions_changed.pop(self.current_session.name)
            index = self.ui.session.currentIndex()
            self.ui.session.setItemIcon(index, gui.generate_qicon())
            if self.current_session.name not in [parser.SIMULATION_NAME,
                                                 parser.REPLAY_NAME]:
                LOGGER.info("Saving '%s' session...", self.current_session.name)
                parser.save_session(parser.CONTROL_PANEL_FILE,
                                    self.current_session)
                LOGGER.info("'%s' session saved.\n", self.current_session.name)
            else:
                LOGGER.error("'%s' is an in-code defined session. "
                             "Impossible to save it !\n",
                             self.current_session.name)
                print("'%s' is an in-code defined session. "
                      "Impossible to save it !\n"
                      % self.current_session.name)
                self.switch_to_tab(3)
        else:
            LOGGER.info("Nothing to save, session already up-to-date.\n")

    def save_all_changed_configurations(self):
        changed_configs = [_ for _ in self.configurations_changed.values()]
        for config in changed_configs:
            self.current_config = config
            self.save_current_config()

    def save_all_changed_sessions(self):
        changed_sessions = [_ for _ in self.sessions_changed.values()]
        for session in changed_sessions:
            self.current_session = session
            self.save_current_session()

    def save_all_changed_data(self):
        self.save_all_changed_configurations()
        self.save_all_changed_sessions()
        print("All changes in data saved.\n")
        self.save_cache_data()

    def save_cache_data(self):
        """
        -> Change values in cache with the 'write' mode of the common parsing
        function.
        """
        LOGGER.info("Cache updating...")
        print("Cache updating...")
        current_geometry_str = " ".join(map(str,
                                            [self.geometry().x(),
                                             self.geometry().y(),
                                             self.geometry().width(),
                                             self.geometry().height()]))
        current_log_filters_str = " ".join(map(str,
                                               [self.current_log_filter.level,
                                                self.current_log_filter.default,
                                                self.current_log_filter.info,
                                                self.current_log_filter.warning,
                                                self.current_log_filter.error]))

        parser.parse_cache_file("w", self.data.cache_file,
                                geometry_str=current_geometry_str,
                                set_str=self.current_set.name,
                                config_str=self.current_config.name,
                                target_str=self.current_target.name,
                                log_filters_str=current_log_filters_str,
                                device_str=self.current_device.name,
                                session_str=self.current_session.name)
        LOGGER.info("Cache now up-to-date.\n")
        print("Cache now up-to-date.\n")
